<script>
import { Teleport, h } from 'vue'
import popIdUtil from "../common/popIdUtil"
import popLayerUtil from "../common/popLayerUtil"
import { createPopper } from '@popperjs/core'
import utils from '../common/utils'
import { getLastMouseDownEvent } from '../common/global'

export default {
    name: 'APopup',
    props: {
        popShown: {
            type: Boolean,
            default: null
        },
        /**
         * 对齐到target的方式
         */
        popPlacement: {
            type: String,
            default: 'bottom'
        },
        popModifiers: {
            type: Array,
            default: []
        },
        getPopModifiersFunc: {
            type: Function,
            default: null
        },
        escToClose: {
            type: Boolean,
            default: true,
        },
        /**
         * 触发弹出框方式，可选click、hover、none，默认为click，设置为none时表示不可使用任何事件弹出框
         */
        trigger: {
            type: String,
            default: 'click'
        },
    },
    data() {
        return {
            popId: null,
            resizeObserver: new ResizeObserver(this.pinToTarget),
            mutationObserver: new MutationObserver(this.pinToTarget),
            prepareToHide: false,
            innerPopShown: false
        }
    },
    computed: {
        myPopShown() {
            if (this.popShown != null) {
                return this.popShown
            }
            return this.innerPopShown
        },
    },
    render() {
        let targetAttrs = Object.assign({
            ref: 'target',
        }, this.$attrs)

        let popAttrs = {
            ref: 'pop',
        }

        let elList = [h(this.$slots.default()[0], targetAttrs)]
        if (this.myPopShown) {

            let popRender = h(this.$slots.pop()[0], popAttrs)

            elList.push(h(Teleport, {
                to: 'body'
            }, popRender))
        }
        return elList
    },
    mounted() {
        this.addTargetEventListener()
        if (this.myPopShown) {
            this.onPopShown()
        }
    },
    watch: {
        myPopShown(val) {
            if (val) {
                this.onPopShown()
            } else {
                this.onPopHidden()
            }
        },
        popModifiers() {
            this.popInstance?.setOptions({
                modifiers: this.getPopModifiers()
            })
        },
    },
    methods: {
        getTargetEl() {
            return utils.getEl(this.$refs.target)
        },
        getPopEl() {
            return utils.getEl(this.$refs.pop)
        },
        addEventListener() {
            window.document.addEventListener('mousedown', this.onDocumentMousedown)
            if (this.escToClose) {
                window.document.addEventListener('keydown', this.onDocumentKeydown)
            }
        },
        removeEventListener() {
            window.document.removeEventListener('mousedown', this.onDocumentMousedown)
            if (this.escToClose) {
                window.document.removeEventListener('keydown', this.onDocumentKeydown)
            }
        },
        onDocumentMousedown(e) {
            let targetEl = this.getTargetEl()

            if (
                popLayerUtil.isOpenedByMeOrMyChildren(targetEl, e.target)
                || targetEl.contains(e.target)
                || !window.document.contains(e.target)
            ) {
                return
            }
            let lastMouseDownEvent = getLastMouseDownEvent()
            if (this.isEventOfChildOfScrollbar(lastMouseDownEvent)) {
                return
            }
            this.hidePop()
        },
        isEventOfChildOfScrollbar(e) {
            if (!e.path) {
                let ele = e.target
                while (ele && ele != window) {
                    if (ele.classList && ele.classList.contains('c-scrollbar')) {
                        return true
                    }
                    ele = ele.parentNode
                }
                return false
            }
            return e.path.some(a => a && a.classList && a.classList.contains('c-scrollbar'))
        },
        onDocumentKeydown(e) {
            if (e.keyCode == 27 && popIdUtil.isTop(this.popId)) {
                this.hidePop()
            }
        },
        onPopShown() {
            if (this.escToClose) {
                this.popId = popIdUtil.create()
            }
            this.$nextTick(() => {
                this.logLayerRelation()
                this.pinToTarget()
                let targetEl = this.getTargetEl()
                this.resizeObserver.observe(targetEl)
                this.mutationObserver.observe(targetEl, { attributes: true, attributeFilter: ['style'] })
                this.addEventListener()
                this.addPopEventListener()
            })
        },
        onPopHidden() {
            this.removeEventListener()

            popIdUtil.remove(this.popId)
            this.closeLayer()

            this.resizeObserver.disconnect()
            this.mutationObserver.disconnect()

            if (this.popInstance) {
                this.popInstance.destroy()
            }
        },
        showPop() {
            this.innerPopShown = true
            this.$emit('update:popShown', true)
        },
        hidePop() {
            this.innerPopShown = false
            this.$emit('update:popShown', false)
        },
        logLayerRelation() {
            let popEl = this.getPopEl()
            if (!popEl) {
                return
            }

            let targetEl = this.getTargetEl()

            popLayerUtil.logLayerRelation(targetEl, popEl)
        },
        closeLayer() {
            let popEl = this.getPopEl()
            if (!popEl) {
                return
            }
            popLayerUtil.closeLayer(popEl)
        },
        pinToTarget() {
            let popEl = this.getPopEl()
            if (!popEl) {
                return
            }

            let targetEl = this.getTargetEl()

            if (this.popInstance) {
                this.popInstance.destroy()
            }

            this.popInstance = createPopper(targetEl, popEl, {
                strategy: 'fixed',
                placement: this.popPlacement,
                modifiers: this.getPopModifiers()
            })
        },
        getPopModifiers() {
            let popModifiers = this.popModifiers
            if (this.getPopModifiersFunc) {
                popModifiers = this.getPopModifiersFunc()
            }
            return popModifiers
        },
        addTargetEventListener() {
            let targetEl = this.getTargetEl()
            if (this.trigger == 'click') {
                targetEl.addEventListener('click', this.onClick)
            }
            else if (this.trigger == 'hover') {
                targetEl.addEventListener('mouseenter', this.onMouseenter)
                targetEl.addEventListener('mouseleave', this.onMouseleave)
            }
        },
        addPopEventListener() {
            let popEl = this.getPopEl()
            if (this.trigger == 'hover') {
                popEl.addEventListener('mouseenter', this.onMouseenter)
                popEl.addEventListener('mouseleave', this.onMouseleave)
            }
        },
        onClick() {
            if (this.myPopShown) {
                this.hidePop()
            } else {
                this.showPop()
            }
        },
        onMouseenter() {
            this.showPop()
            this.prepareToHide = false
        },
        onMouseleave() {
            this.prepareToHide = true
            setTimeout(() => {
                if (this.prepareToHide) {
                    this.hidePop()
                }
            }, 50)
        },
    },
    unmounted() {
        this.onPopHidden()
    }
}
</script>